import constants from '@/constants';
import { BaseService } from '@/core/base.service';
import { CaeeService } from '@/core/caee.service';
import { HuifuService } from '@/core/huifu.service';
import { SandService } from '@/core/sand.service';
import { UopService } from '@/core/uop.service';
import { YopService } from '@/core/yop.service';
import {
  AssetAirdropDto,
  AssetBurnDto,
  AssetCirculationDto,
  AssetConsignmentDto,
  AssetDeployDto,
  AssetDestroyDto,
  AssetDistributionDto,
  AssetHoldDto,
  AssetLockDto,
  AssetMintDto,
  AssetPreDto,
  AssetQueryDto,
  AssetReceiveDto,
  AssetRegistrationDto,
  AssetSaleDto,
  AssetSyncDto,
  AssetTargetedSaleDto,
  AssetTokensDto,
  AssetTransferDto,
  AssetUnLockDto,
  MarketBatchDownDto,
  MarketBatchUpDto,
  MarketDownDto,
} from '@/dtos';
import {
  BillStatusEnum,
  BillTypeEnum,
  ChainTxStateEnum,
  OrderStatusEnum,
  OrderTypeEnum,
  PaymentTypeEnum,
  RightsStatusEnum,
  RightsTypeEnum,
  TokenLogTypeEnum,
  TokenSourceEnum,
  TokenStatusEnum,
} from '@/enums';
import { compareHash, getCode, getNanoid, halt, ok } from '@/utils';
import { InjectQueue } from '@nestjs/bull';
import { Injectable, Logger } from '@nestjs/common';
import { Asset, Prisma } from '@prisma/client';
import { Queue } from 'bull';
import dayjs from 'dayjs';
import Decimal from 'decimal.js';
import getMAC from 'getmac';
import { SettingService } from './setting.service';
import { BillService } from './bill.service';
import { OrderService } from './order.service';
import { WishStatusEnum } from '@/enums/wish.enum';
import { WishService } from '@/services/wish.service';

@Injectable()
export class AssetService extends BaseService {
  constructor(
    @InjectQueue('chain')
    private readonly chainQueue: Queue,
    @InjectQueue('order')
    private readonly orderQueue: Queue,
    private readonly settingService: SettingService,
    private readonly uopService: UopService,
    private readonly yopService: YopService,
    private readonly huifuService: HuifuService,
    private readonly sandService: SandService,
    private readonly caeeService: CaeeService,
    private readonly billService: BillService,
    private readonly wishService: WishService,
  ) {
    super();
  }

  async hold({
    userId,
    assetId,
    albumId,
    type,
    status,
    params,
    sort,
  }: AssetHoldDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    const sql = `
      SELECT #{fields} FROM Token AS t
      LEFT JOIN Asset AS a ON a.id = t.assetId
      LEFT JOIN Album AS aa ON aa.id = a.albumId
      LEFT JOIN Publisher AS p ON p.id = a.publisherId
      WHERE t.isDeleted = false
        AND t.ownerId = ${userId}
        ${!!userId && Number(userId) > 0 ? `AND t.ownerId = ${userId}` : ''}
        ${!!assetId && Number(assetId) > 0 ? `AND a.id = ${assetId}` : ''}
        ${!!status && Number(status) > 0 ? `AND t.status = ${status}` : ''}
        ${!!albumId && Number(albumId) > 0 ? `AND aa.id = ${albumId}` : ''}
        ${!!where.title ? `AND t.title LIKE '%${where.title}%'` : ''}
    `;

    // ${!!type && Number(type) > 0 ? `AND aa.type = ${type}` : ''}

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(DISTINCT a.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Asset[]>(
      `${sql.replace(
        '#{fields}',
        `
          a.id, a.title, a.image, p.name AS publisherName, p.logo AS publisherLogo,
          COUNT(t.id) AS num, t.status,
          AVG(t.bidPrice) AS price,
          (
            SELECT COUNT(1)
            FROM UserFollowAsset AS ufa 
            WHERE ufa.assetId = t.assetId 
              AND ufa.userId = ${userId}
          ) AS isFollow
        `,
      )}
      GROUP BY a.id, t.assetId, t.status
      ORDER BY ${
        !!sort && Object.keys(sort).length > 0
          ? Object.entries(sort)
              .map(([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`)
              .join(',')
          : 'a.createdAt DESC'
      }
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o: any) => ({
        ...o,
        num: Number(o.num),
        price: Number(o.price).toFixed(2),
        isFollow: Number(o.isFollow) > 0,
      })),
    };
  }

  async tokens({ id, userId, params, sort }: AssetTokensDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    let sql = `
      SELECT #{fields} FROM Token AS t
      LEFT JOIN Asset AS a ON a.id = t.assetId
      LEFT JOIN Album AS aa ON aa.id = a.albumId
      LEFT JOIN Publisher AS p ON p.id = a.publisherId
      WHERE t.isDeleted = false
        AND t.ownerId = ${userId}        
        AND t.assetId = ${id}
        AND (
          t.status = ${TokenStatusEnum.HOLD}
          OR
          t.status = ${TokenStatusEnum.LOCKING}
        )
    `;

    if (id === 0) {
      sql = `
      SELECT #{fields} FROM Token AS t
      LEFT JOIN Asset AS a ON a.id = t.assetId
      LEFT JOIN Album AS aa ON aa.id = a.albumId
      LEFT JOIN Publisher AS p ON p.id = a.publisherId
      WHERE t.isDeleted = false
        AND t.ownerId = ${userId}        
        AND t.status = ${TokenStatusEnum.HOLD}
        AND a.destroyPoints > 0
    `;
    }

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Asset[]>(
      `${sql.replace(
        '#{fields}',
        `
          t.id, t.no, t.bidPrice, t.price, t.bidAt, t.sellAt, t.source, t.status, 
          t.hash, t.isTradable, a.isUseable, a.isReceivable, a.isShipped,
          a.lowestPrice, a.highestPrice, a.title, a.image, a.limit, a.circulation, 
          p.name AS publisherName, p.logo AS publisherLogo
        `,
      )}      
      ORDER BY t.hash DESC
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o: any) => ({
        ...o,
        lowestPrice: Number(o.lowestPrice).toFixed(2),
        highestPrice: Number(o.highestPrice).toFixed(2),
        bidPrice: Number(o.bidPrice).toFixed(2),
        price: Number(o.price).toFixed(2),
        isSelect: false,
      })),
    };
  }

  async tokens2({ id, userId, params, sort }: AssetTokensDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    const sql = `
      SELECT #{fields} FROM Token AS t
      LEFT JOIN Asset AS a ON a.id = t.assetId
      WHERE t.isDeleted = false
        AND t.ownerId = ${userId}        
        AND t.status = ${TokenStatusEnum.HOLD}
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Asset[]>(
      `${sql.replace(
        '#{fields}',
        `
          t.id, t.no, a.title, a.image
        `,
      )}      
      ORDER BY a.id DESC
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list,
    };
  }

  async getUserConsignment({
    userId,
    status = TokenStatusEnum.ON_SALE,
    albumId,
    params,
  }: AssetConsignmentDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    const sql = `
      SELECT #{fields} FROM Token AS t
      LEFT JOIN Asset AS a ON a.id = t.assetId
      LEFT JOIN Album AS aa ON aa.id = a.albumId
      LEFT JOIN Publisher AS p ON p.id = a.publisherId
      WHERE t.isDeleted = false
        AND t.ownerId = ${userId}
        AND t.status = ${status}
        ${!!albumId && Number(albumId) > 0 ? `AND aa.id = ${albumId}` : ''}
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Asset[]>(
      `${sql.replace(
        '#{fields}',
        `
          t.id, t.no, t.bidPrice, t.price, t.status, a.lowestPrice, 
          a.highestPrice, a.title, a.image, a.limit, a.circulation, 
          p.name AS publisherName, p.logo AS publisherLogo
        `,
      )}      
      ORDER BY t.sellAt DESC
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o: any) => ({
        ...o,
        lowestPrice: Number(o.lowestPrice).toFixed(2),
        highestPrice: Number(o.highestPrice).toFixed(2),
        bidPrice: Number(o.bidPrice).toFixed(2),
        price: Number(o.price).toFixed(2),
      })),
    };
  }

  async onSale({ id, userId, price, pass }: AssetSaleDto) {
    await this.redisLock('on_sale', userId);
    const setting = await this.settingService.getDefault();
    if (!!setting.isTradable) {
      return halt('交易暂停');
    }
    const user = await this.prisma.user.findUnique({
      where: { id: userId, isBlockd: false, isDeleted: false },
    });
    if (!user) {
      return halt('用户信息异常');
    }
    if (!user.isReal) {
      return halt('请先完成实名认证');
    }
    if (!user.txPass) {
      return halt('请先设置操作密码');
    }
    if (!(await compareHash(pass, user.txPass))) {
      return halt('密码错误');
    }
    if (this.config.get('payment') === PaymentTypeEnum.YOP) {
    } else if (this.config.get('payment') === PaymentTypeEnum.HUIFU) {
      if (!user.userCustId || !user.acctId) {
        return halt('汇付钱包异常');
      }
    } else if (this.config.get('payment') === PaymentTypeEnum.SNAD) {
      if (!user.sandId) {
        return halt('杉徳钱包异常');
      }
    }
    const token = await this.prisma.token.findUnique({
      where: {
        id,
        ownerId: userId,
        isDeleted: false,
      },
      select: {
        id: true,
        status: true,
        sellerId: true,
        operationId: true,
        txState: true,
        isTradable: true,
        hash: true,
        asset: {
          select: {
            id: true,
            highestPrice: true,
            lowestPrice: true,
            isTradable: true,
            isWish: true,
          },
        },
      },
    });
    if (!token) {
      return halt('数据异常');
    }
    if (!token.asset.isTradable) {
      return halt('暂未开放交易');
    }
    if (!token.isTradable) {
      return halt('暂未开放交易');
    }
    if (price < Number(token.asset.lowestPrice)) {
      return halt(`寄售价格不能低于 ${token.asset.lowestPrice} 元`);
    }
    if (price > Number(token.asset.highestPrice)) {
      return halt(`寄售价格不能高于 ${token.asset.highestPrice} 元`);
    }

    if (token.asset.isWish) {
      // 求购查询
      const wishOrders = await this.prisma.wish.findMany({
        where: {
          assetId: token.asset.id,
          status: WishStatusEnum.PREPAID,
          uid: {
            not: userId,
          },
          price: {
            gte: price, // >=
          },
        },
        orderBy: {
          payPrice: 'desc',
          // id: 'asc',
        },
      });

      if (wishOrders.length > 0) {
        // return halt(`有符合条件求购订单`);
        try {
          return ok(
            await this.wishService.transaction(
              {
                no: wishOrders[0].no,
                uid: userId,
              },
              token.id,
            ),
          );
        } catch (e) {
          console.log(`求购失败：${wishOrders[0].no} => ${e.message}`);
        }
      }
    }

    return await this.prisma.$transaction(async (tx) => {
      const amount = Number(price);
      const hookId = await this.redis.srandmember(`hook:${token.asset.id}`);
      if (!!hookId && Number(hookId) !== userId) {
        const toUser = await tx.user.findUnique({
          where: {
            id: Number(hookId),
          },
        });
        if (!!toUser && Number(toUser.balance) >= amount) {
          await tx.token.update({
            where: {
              id: token.id,
            },
            data: {
              price,
              sellAt: new Date(),
              sellerId: userId,
            },
          });
          await tx.tokenLog.create({
            data: {
              log: `寄售${price}￥`,
              type: TokenLogTypeEnum.ON_SALE,
              tokenId: token.id,
              fromId: userId,
            },
          });

          const setting = await this.settingService.getDefault();
          const fee = amount * Number(setting.fee);
          const currentTime = dayjs();
          const expireTime = dayjs(currentTime).add(3, 'minutes');

          let no = null;
          const payNo = null;
          const payUrl = null;
          if (this.config.get('isUseBalance')) {
            no = await this.uopService.generateOrderNo();
          }
          const order = await tx.order.create({
            data: {
              no,
              num: 1,
              total: amount,
              fee,
              expireTime: expireTime.toDate(),
              assetId: token.asset.id,
              ownerId: toUser.id,
              payType: this.config.get('payment'),
              payNo,
              payUrl,
              type: OrderTypeEnum.MARKET,
              status: OrderStatusEnum.PENDING,
              isTargetedSale: true,
            },
          });
          await tx.subOrder.create({
            data: {
              no: await this.uopService.generateOrderNo(),
              price: amount,
              fee,
              orderId: order.id,
              assetId: token.asset.id,
              tokenId: token.id,
              buyerId: toUser.id,
              sellerId: userId,
            },
          });
          const subOrders = await tx.subOrder.findMany({
            where: {
              orderId: order.id,
              buyerId: toUser.id,
            },
            select: {
              id: true,
              price: true,
              fee: true,
              token: {
                select: {
                  id: true,
                  no: true,
                  price: true,
                  classId: true,
                  asset: true,
                },
              },
              seller: {
                select: {
                  id: true,
                  mobile: true,
                  account: true,
                  version: true,
                },
              },
            },
          });
          for (const o of subOrders) {
            const fee = Number(o.fee);
            const amount = Number(o.price) - Number(o.fee);
            await tx.token.update({
              where: {
                id: o.token.id,
              },
              data: {
                bidPrice: Number(o.price),
                bidAt: new Date(),
                ownerId: toUser.id,
                source: TokenSourceEnum.TRADE,
                status: TokenStatusEnum.HOLD,
              },
            });
            const seller = await tx.user.findUnique({
              where: {
                id: o.seller.id,
              },
            });
            await tx.user.update({
              where: {
                id: seller.id,
                version: seller.version,
              },
              data: {
                version: {
                  increment: 1,
                },
                balance: {
                  increment: amount,
                },
              },
            });
            await tx.tokenLog.create({
              data: {
                log: `买入${o.price}￥`,
                type: TokenLogTypeEnum.TRANSFER,
                tokenId: o.token.id,
                fromId: o.seller.id,
                recipientId: toUser.id,
              },
            });
            await tx.bill.create({
              data: {
                no: await this.generateOrderNo(),
                total: amount,
                fee,
                type: BillTypeEnum.SELL,
                status: BillStatusEnum.COMPLETED,
                ownerId: o.seller.id,
                remark: `卖出《${o.token.asset.title}》#${o.token.no}`,
              },
            });
          }
          await tx.user.update({
            where: {
              id: toUser.id,
              version: toUser.version,
            },
            data: {
              version: {
                increment: 1,
              },
              balance: {
                decrement: amount,
              },
            },
          });
          await tx.bill.create({
            data: {
              no: await this.generateOrderNo(),
              total: amount,
              fee,
              type: BillTypeEnum.BUY,
              status: BillStatusEnum.COMPLETED,
              ownerId: toUser.id,
              orderId: order.id,
              remark: `支付订单: ${order.no}`,
            },
          });
          await tx.order.update({
            where: {
              id: order.id,
            },
            data: {
              payTime: new Date(),
              status: OrderStatusEnum.COMPLETED,
            },
          });
          return true;
        }
      }
      await tx.token.update({
        where: {
          id: token.id,
          ownerId: userId,
          status: TokenStatusEnum.HOLD,
        },
        data: {
          price,
          sellAt: new Date(),
          sellerId: userId,
          status: TokenStatusEnum.ON_SALE,
        },
      });
      await tx.tokenLog.create({
        data: {
          log: `寄售${price}￥`,
          type: TokenLogTypeEnum.ON_SALE,
          tokenId: token.id,
          fromId: userId,
        },
      });
      return true;
    });
  }

  async offSale(id: number, userId: number) {
    return await this.prisma.$transaction(async (tx) => {
      await tx.token.update({
        where: {
          id,
          ownerId: userId,
          status: TokenStatusEnum.ON_SALE,
        },
        data: {
          status: TokenStatusEnum.HOLD,
        },
      });
      await tx.tokenLog.create({
        data: {
          log: `取消寄售`,
          type: TokenLogTypeEnum.OFF_SALE,
          tokenId: id,
          fromId: userId,
        },
      });
      return true;
    });
  }

  async query({ params, sort }: AssetQueryDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    const sql = `
            SELECT #{fields} FROM Asset AS t
            LEFT JOIN Album AS a ON a.id = t.albumId
            WHERE t.isDeleted = false
                ${!!where.title ? `AND t.title LIKE '%${where.title}%'` : ''}
                ${!!where.albumId ? `AND t.albumId = ${where.albumId}` : ''}
                ${!!where.publisherId ? `AND t.publisherId = ${where.publisherId}` : ''}
                ${!!where.creatorId ? `AND t.creatorId = ${where.creatorId}` : ''}
                ${!!where.isTradable ? `AND t.isTradable = ${where.isTradable}` : ''}
        `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Asset[]>(
      `${sql.replace(
        '#{fields}',
        `
            t.id, t.title, t.limit, t.price, t.image, t.circulation,
            t.highestPrice, t.lowestPrice, t.isTradable, t.txState,
            t.albumId, t.publisherId, t.creatorId, t.desc, t.detail,
            t.releaseNo, t.seriesNo, t.isPreRegistration, t.isRegistration,
            t.assetId, t.preRegistrationHash, t.registrationHash,
            t.registrationInfo, t.releasePrice, t.releaseDate, 
            t.operationId, a.title AS album, a.type, t.isWish,
            t.isReceivable, t.isShipped, t.rightsType, t.rightsLimit, t.isUseable, t.destroyPoints,
            (
                SELECT COUNT(o.id) 
                FROM Token AS o 
                WHERE 
                        t.id = o.assetId 
                    AND
                        o.txState = '${ChainTxStateEnum.PENDING}'
            ) AS mintPending,
            (
                SELECT COUNT(o.id) 
                FROM Token AS o 
                WHERE 
                        t.id = o.assetId 
                    AND
                        o.txState = '${ChainTxStateEnum.UN_COMMIT}'
            ) AS mintUnCommit,
            (
                SELECT COUNT(o.id) 
                FROM Token AS o 
                WHERE 
                        t.id = o.assetId 
                    AND
                        o.txState = '${ChainTxStateEnum.SUCCESS}'
            ) AS mintSuccess,
            (
                SELECT COUNT(o.id) 
                FROM Token AS o 
                WHERE 
                        t.id = o.assetId 
                    AND
                        o.txState = '${ChainTxStateEnum.FAILED}'
            ) AS mintFailed,
            (
                SELECT COUNT(o.id) 
                FROM Token AS o 
                WHERE 
                        t.id = o.assetId 
                    AND
                        o.isRegistration = false
            ) AS unRegistered
            `,
      )}
            ORDER BY ${
              !!sort && Object.keys(sort).length > 0
                ? Object.entries(sort)
                    .map(
                      ([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`,
                    )
                    .join(',')
                : 't.createdAt DESC'
            }
            LIMIT ${pageSize} 
            OFFSET ${(current - 1) * pageSize}
        `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o) => ({
        ...o,
        price: Number(o.price),
        highestPrice: Number(o.highestPrice),
        lowestPrice: Number(o.lowestPrice),
        releasePrice: Number(o.releasePrice),
      })),
    };
  }

  async findOne(
    where: Prisma.AssetWhereUniqueInput,
    select?: Prisma.AssetSelect,
  ): Promise<Asset | null> {
    return this.prisma.asset.findUnique({
      where,
      select,
    });
  }

  async create(data: Prisma.AssetCreateInput): Promise<Asset> {
    return await this.prisma.asset.create({
      data: {
        ...data,
        ...(data.releaseDate
          ? {
              releaseDate: dayjs(data.releaseDate.toString()).toDate(),
            }
          : {}),
      },
    });
  }

  async update(params: {
    where: Prisma.AssetWhereUniqueInput;
    data: Prisma.AssetUpdateInput;
  }): Promise<Asset> {
    const { where, data } = params;
    return this.prisma.asset.update({
      where,
      data: {
        ...data,
        ...(data.releaseDate
          ? {
              releaseDate: dayjs(data.releaseDate.toString()).toDate(),
            }
          : {}),
      },
    });
  }

  async deploy({ id }: AssetDeployDto) {
    const asset = await this.prisma.asset.findUnique({
      where: {
        id,
        isDeleted: false,
      },
    });
    if (!asset) {
      return halt('目标资产不存在');
    }
    if (asset?.txState === ChainTxStateEnum.SUCCESS) {
      return halt('该资产已成功部署');
    }
    let operationId = asset?.operationId;
    let classId = asset?.classId;
    if (!operationId) {
      operationId = await this.chain.generateOperationId();
      const res = await this.chain.classesDeploy({
        name: await getNanoid(20),
        symbol: operationId,
        operationId,
      });
      if (res?.code !== 200) {
        return halt(`部署资产类别失败: ${res?.message ?? '未知错误'}`);
      }
      classId = res?.data?.classId;
      console.log('提交资产类别部署:', operationId, res);
    }
    const txRes = await this.chain.nftTx({
      operationId,
    });
    if (txRes?.code !== 200) {
      return halt(`查询资产类别结果失败: ${txRes?.message ?? '未知错误'}`);
    }
    await this.prisma.asset.update({
      where: {
        id,
      },
      data: {
        classId,
        operationId,
        txState: txRes?.data?.state,
        txHash: txRes?.data?.txHash,
      },
    });
    return txRes;
  }

  async mint({ id }: AssetMintDto) {
    const owner = await this.prisma.user.findUnique({
      where: {
        id: constants.default.ownerId,
      },
    });
    if (!owner) return halt('仓库信息异常');
    const asset = await this.prisma.asset.findUnique({
      where: {
        id,
        isDeleted: false,
      },
    });
    if (!asset) {
      return halt('目标资产不存在');
    }
    for (let i = 0; i < asset.limit; i++) {
      const no = (i + 1).toString();
      console.log(`${asset.title}#${no}`);
      let token = await this.prisma.token.findFirst({
        where: {
          no,
          assetId: id,
        },
      });
      if (!token) {
        token = await this.prisma.token.create({
          data: {
            no,
            classId: asset.classId,
            assetId: asset.id,
            ownerId: constants.default.ownerId,
            status: TokenStatusEnum.HOLD,
            hash: await this.chain.createHash(),
          },
        });
      }
    }
    return true;
  }

  async sync({ id, txState }: AssetSyncDto) {
    const owner = await this.prisma.user.findUnique({
      where: {
        id: constants.default.ownerId,
      },
    });
    if (!owner) return halt('仓库信息异常');
    const asset = await this.prisma.asset.findUnique({
      where: {
        id,
        isDeleted: false,
      },
    });
    if (!asset) {
      return halt('目标资产不存在');
    }
    const tokens = await this.prisma.token.findMany({
      where: {
        assetId: asset.id,
        txState,
      },
    });
    for (const token of tokens) {
      let operationId = token?.operationId;
      console.log(
        `准备铸造: ${asset.title}#${token.no}`,
        operationId,
        token?.txState,
      );
      if (!operationId || token?.txState === ChainTxStateEnum.FAILED) {
        operationId = await this.chain.generateOperationId();
        const tokenId = await getCode(20);
        const res = await this.chain.nftMint({
          classId: asset.classId,
          recipient: owner.account,
          tokenId: tokenId,
          operationId,
        });
        if (res?.code !== 200) {
          console.log(
            `${asset.title}#${token.no}铸造失败: ${res?.message ?? '未知错误'}`,
          );
        } else {
          console.log(`提交铸造交易: ${asset.title}#${token.no}`, res);
          await this.prisma.token.update({
            where: {
              id: token.id,
            },
            data: {
              operationId,
              tokenId,
            },
          });
        }
      }
      [10000].forEach(async (delay) => {
        await this.chainQueue.add(
          'mint',
          {
            id: token.id,
          },
          {
            delay,
          },
        );
      });
    }
  }

  async targetedSale({
    hash,
    to,
    price,
    discount = 0,
    ip,
  }: AssetTargetedSaleDto) {
    return await this.prisma.$transaction(async (tx) => {
      const setting = await this.settingService.getDefault();
      const token = await tx.token.findUnique({
        where: {
          hash,
          status: TokenStatusEnum.HOLD,
          isDeleted: false,
        },
        select: {
          id: true,
          classId: true,
          no: true,
          price: true,
          sellerId: true,
          ownerId: true,
          owner: true,
          asset: true,
        },
      });
      if (!token) {
        return halt('目标资产信息异常');
      }
      const toUser = await tx.user.findUnique({ where: { id: to } });
      if (!toUser) {
        return halt(`目标用户信息异常`);
      }

      // const count = await this.prisma.order.count({
      //   where: {
      //     ownerId: toUser.id,
      //     status: OrderStatusEnum.PENDING,
      //     isDeleted: false,
      //   },
      // });
      // if (count > 0) {
      //   return halt('买家有待支付订单');
      // }

      let divideDetail = [];

      const amount =
        discount > 0 ? Number(price) * Number(discount) : Number(price);
      const fee = amount * Number(setting.fee);

      if (amount > Number(toUser.balance)) {
        return halt('余额不足');
      }

      if (this.config.get('payment') === PaymentTypeEnum.YOP) {
        const res = await this.yopService.memberQuery(
          `${token.owner.id}-${token.owner.code}`,
        );
        if (!res || !res.isSuccess) {
          return halt('卖家钱包信息异常');
        }
        console.log(`用户${token.owner.id}钱包账号: `, res.result);
        divideDetail.push({
          amount: new Decimal(amount).toDecimalPlaces(2).toString(),
          ledgerNo: res.result.memberNo,
          ledgerType: 'MERCHANT2MEMBER',
        });
      }

      const currentTime = dayjs();
      const expireTime = dayjs(currentTime).add(3, 'minutes');

      let no = null;
      let payNo = null;
      let payUrl = null;
      if (this.config.get('isUseBalance')) {
        no = await this.uopService.generateOrderNo();
      } else {
        if (this.config.get('payment') === PaymentTypeEnum.YOP) {
          divideDetail = [
            {
              amount: new Decimal(fee).toDecimalPlaces(2).toString(),
              ledgerNo: this.config.get('yop.merCustId'),
              ledgerType: 'MERCHANT2MERCHANT',
            },
            ...divideDetail,
          ];
          no = await this.yopService.generateOrderNo();
          const res = await this.yopService.order({
            orderId: no,
            orderAmount: new Decimal(amount).toDecimalPlaces(2).toNumber(),
            goodsName: `${token.asset.title} #${token.no}`,
            returnUrl: `${this.yopService.ReturnUrl}/mine`,
            notifyUrl: `${this.yopService.NotifyUrl}/api/order/callback`,
            fundProcessType: 'REAL_TIME_DIVIDE',
            divideDetail,
            businessInfo: {
              collectionSeries: `${token.asset.id}`,
              collectionName: `${token.asset.title}`,
              contractModel: '721',
              collectionId: `${token.id}`,
              marketType: '转卖（二级市场）',
              userRegisterMobile: toUser.mobile,
              registTime: dayjs(toUser.createdAt).format('YYYY-MM-DD HH:mm:ss'),
              registIp: ip,
              userRegisterIdNo: `${toUser.idCard}`,
              registId: `${toUser.id}`,
            },
          });
          if (!res || !res.isSuccess || res?.result?.code !== '00000') {
            return halt(res?.result?.message ?? '易宝订单异常');
          }
          payNo = res.result.uniqueOrderNo;
          payUrl = res.result.cashierUrl;
        } else if (this.config.get('payment') === PaymentTypeEnum.HUIFU) {
          if (!toUser.userCustId || !toUser.acctId) {
            return halt('请先开通汇付钱包');
          }
          no = await this.huifuService.generateOrderNo();
          const res = await this.huifuService.pay033({
            order_date: dayjs(currentTime).format('YYYYMMDD'),
            order_id: no,
            order_expire_time: dayjs(expireTime).format('YYYYMMDDHHmmss'),
            user_cust_id: toUser.userCustId,
            trans_amt: Number(amount).toFixed(2),
            goods_desc: `${token.asset.title}`,
            div_type: '0',
            div_details: JSON.stringify([
              {
                riskDivType: '1',
                divCustId:
                  token.owner.id === constants.default.ownerId
                    ? this.config.get('huifu.merCustId')
                    : token.owner.userCustId,
                divAcctId:
                  token.owner.id === constants.default.ownerId
                    ? this.config.get('huifu.merAcctId')
                    : token.owner.acctId,
                divAmt: new Decimal(amount).toFixed(2),
              },
              // {
              //   riskDivType: '2',
              //   divCustId: this.config.get('huifu.merCustId'),
              //   divAcctId: this.config.get('huifu.merAcctId'),
              //   divAmt: new Decimal(fee).toFixed(2),
              // },
            ]),
            dev_info_json: JSON.stringify({
              devType: '2',
              ipAddr: ip,
              MAC: getMAC().replaceAll(':', ''),
            }),
            object_info: JSON.stringify({
              objectName: `${token.asset.title}`,
              marketType: 2,
            }),
          });
          if (res?.resp_code !== 'C00000') {
            return halt(res?.resp_desc ?? '获取支付地址失败');
          }
          payNo = res.order_id;
          payUrl = res.pay_url;
        } else if (this.config.get('payment') === PaymentTypeEnum.SNAD) {
          no = await this.sandService.generateOrderNo();
          const res = await this.sandService.order({
            orderNo: no,
            orderAmt: new Decimal(amount).toDecimalPlaces(2).toString(),
            ip,
            returnUrl: `${this.sandService.ReturnUrl}/mine`,
            notifyUrl: `${this.sandService.NotifyUrl}/api/order/callback`,
            goodsName: `${token.asset.title} #${token.no}`,
            extra: JSON.stringify({
              operationType: '1',
              recvUserId: token.owner.sandId,
              bizType: '2',
              payUserId: toUser.sandId,
              userFeeAmt: new Decimal(fee).toDecimalPlaces(2).toString(),
            }),
          });
          if (!res || !res.no || !res.url) {
            return halt('杉徳订单异常');
          }
          payNo = res.no;
          payUrl = res.url;
        } else {
          return halt('支付方式异常');
        }
      }

      const order = await tx.order.create({
        data: {
          no,
          num: 1,
          total: amount,
          discount,
          fee,
          expireTime: expireTime.toDate(),
          assetId: token.asset.id,
          ownerId: toUser.id,
          payType: this.config.get('payment'),
          payNo,
          payUrl,
          type: OrderTypeEnum.MARKET,
          status: OrderStatusEnum.PENDING,
          isTargetedSale: true,
        },
      });

      await tx.token.update({
        where: {
          hash,
          status: TokenStatusEnum.HOLD,
        },
        data: {
          status: TokenStatusEnum.LOCKING,
          price,
          sellAt: new Date(),
          sellerId: token.ownerId,
        },
      });

      await tx.subOrder.create({
        data: {
          no: await this.uopService.generateOrderNo(),
          price: amount,
          fee,
          orderId: order.id,
          assetId: token.asset.id,
          tokenId: token.id,
          buyerId: toUser.id,
          sellerId: token.ownerId,
        },
      });

      if (!setting.isHideTargetedOrder) {
        await this.orderQueue.add(
          'cancel',
          {
            no: order.no,
          },
          {
            delay: expireTime.diff(dayjs()),
          },
        );
      } else {
        const subOrders = await tx.subOrder.findMany({
          where: {
            orderId: order.id,
            buyerId: toUser.id,
          },
          select: {
            id: true,
            price: true,
            fee: true,
            token: {
              select: {
                id: true,
                no: true,
                price: true,
                classId: true,
                asset: true,
              },
            },
            seller: {
              select: {
                id: true,
                mobile: true,
                account: true,
                version: true,
              },
            },
          },
        });
        for (const o of subOrders) {
          const fee = Number(o.fee);
          const amount = Number(o.price) - Number(o.fee);
          await tx.token.update({
            where: {
              id: o.token.id,
            },
            data: {
              bidPrice: Number(o.price),
              bidAt: new Date(),
              ownerId: toUser.id,
              source: TokenSourceEnum.TRADE,
              status: TokenStatusEnum.HOLD,
            },
          });
          const seller = await tx.user.findUnique({
            where: {
              id: o.seller.id,
            },
          });
          await tx.user.update({
            where: {
              id: seller.id,
              version: seller.version,
            },
            data: {
              version: {
                increment: 1,
              },
              balance: {
                increment: amount,
              },
            },
          });
          await tx.tokenLog.create({
            data: {
              log: `买入${o.price}￥`,
              type: TokenLogTypeEnum.TRANSFER,
              tokenId: o.token.id,
              fromId: o.seller.id,
              recipientId: toUser.id,
            },
          });
          await tx.bill.create({
            data: {
              no: await this.generateOrderNo(),
              total: amount,
              fee,
              type: BillTypeEnum.SELL,
              status: BillStatusEnum.COMPLETED,
              ownerId: o.seller.id,
              remark: `卖出《${o.token.asset.title}》#${o.token.no}`,
            },
          });
        }
        if (order.type === OrderTypeEnum.INITIAL) {
          await tx.asset.update({
            where: {
              id: order.assetId,
            },
            data: {
              circulation: {
                increment: order.num,
              },
            },
          });
        }
        await tx.user.update({
          where: {
            id: toUser.id,
            version: toUser.version,
          },
          data: {
            version: {
              increment: 1,
            },
            balance: {
              decrement: amount,
            },
          },
        });
        await tx.bill.create({
          data: {
            no: await this.generateOrderNo(),
            total: amount,
            fee,
            type: BillTypeEnum.BUY,
            status: BillStatusEnum.COMPLETED,
            ownerId: toUser.id,
            orderId: order.id,
            remark: `支付订单: ${order.no}`,
          },
        });
        await tx.order.update({
          where: {
            id: order.id,
          },
          data: {
            payTime: new Date(),
            status: OrderStatusEnum.COMPLETED,
          },
        });
      }
    });
  }

  async transfer({
    id,
    from,
    to,
    targets,
    source = TokenSourceEnum.MINT,
  }: AssetTransferDto) {
    return await this.prisma.$transaction(async (tx) => {
      const asset = await tx.asset.findUnique({
        where: {
          id,
          isDeleted: false,
        },
      });
      if (!asset) {
        return halt('目标资产不存在');
      }
      const fromUser = await tx.user.findUnique({ where: { id: from } });
      if (!fromUser) {
        return halt(`源用户信息异常`);
      }
      const toUser = await tx.user.findUnique({ where: { id: to } });
      if (!toUser) {
        return halt(`目标用户信息异常`);
      }
      if (!!targets && targets.length > 0) {
        for (const target of targets) {
          const token = await tx.token.findUnique({
            where: {
              isDeleted: false,
              ownerId: from,
              assetId: id,
              id: target,
            },
          });
          if (!token) {
            return halt(`目标资产信息异常: ${target}`);
          }
          await tx.token.update({
            where: {
              id: target,
              assetId: id,
              ownerId: from,
            },
            data: {
              ownerId: to,
              source,
              status: TokenStatusEnum.HOLD,
            },
          });
          await tx.tokenLog.create({
            data: {
              log: `后台转让: ${token.no}`,
              type: TokenLogTypeEnum.TRANSFER,
              tokenId: token.id,
              fromId: fromUser.id,
              recipientId: toUser.id,
            },
          });
        }
      }
    });
  }

  async airdrop({ id, rows }: AssetAirdropDto) {
    const asset = await this.prisma.asset.findUnique({
      where: {
        id,
        isDeleted: false,
      },
    });
    if (!asset) {
      return halt('目标资产不存在');
    }
    for (const { mobile, num } of rows) {
      const user = await this.prisma.user.findFirst({
        where: {
          mobile,
        },
      });
      if (!user) {
        continue;
      }
      const count = await this.prisma.token.count({
        where: {
          assetId: id,
          ownerId: constants.default.ownerId,
          isDeleted: false,
        },
      });
      if (count < num) {
        return halt('可空投数量不足');
      }
      for (let i = 0; i < num; i++) {
        const target = await this.prisma.token.findFirst({
          where: {
            assetId: id,
            ownerId: constants.default.ownerId,
            isDeleted: false,
          },
          orderBy: {
            hash: 'desc',
          },
        });
        await this.transfer({
          id,
          from: constants.default.ownerId,
          to: user.id,
          targets: [target.id],
          source: TokenSourceEnum.COMPOSE,
        });
      }
    }
    return true;
  }

  async burn({ id, no }: AssetBurnDto) {
    return await this.prisma.$transaction(async (tx) => {
      const token = await tx.token.findFirst({
        where: {
          asset: { id },
          no,
          isDeleted: false,
        },
      });
      if (!token) {
        return halt('资产不存在');
      }
      await tx.token.update({
        where: {
          id: token.id,
        },
        data: {
          isDeleted: true,
          status: TokenStatusEnum.DESTROYED,
        },
      });
      const asset = await tx.asset.findUnique({
        where: {
          id: token.assetId,
        },
      });
      await tx.asset.update({
        where: {
          id: asset.id,
        },
        data: {
          circulation: {
            decrement: 1,
          },
        },
      });
      await tx.tokenLog.create({
        data: {
          log: `销毁: ${token.no}`,
          type: TokenLogTypeEnum.BURN,
          tokenId: token.id,
          fromId: token.ownerId,
        },
      });
      return true;
    });
  }

  async receive({ id, addressId }: AssetReceiveDto, userId: number) {
    return await this.redlock.using(
      [`asset:receive:${userId}`],
      10000,
      {},
      async () => {
        return await this.prisma.$transaction(async (tx) => {
          const user = await tx.user.findUnique({
            where: {
              id: userId,
              isBlockd: false,
              isDeleted: false,
            },
          });
          if (!user) {
            return halt('用户信息异常');
          }
          if (!user.isReal) {
            return halt('请先完成实名认证');
          }
          const token = await tx.token.findUnique({
            where: {
              id,
              status: TokenStatusEnum.HOLD,
            },
            select: {
              id: true,
              no: true,
              asset: true,
            },
          });
          if (!token) {
            return halt('资产信息异常');
          }

          if (!token.asset?.isReceivable) {
            return halt('该资产未开放核销通道');
          }

          let address = null;
          if (!!token.asset?.isShipped) {
            if (!!addressId) {
              address = await tx.address.findUnique({
                where: {
                  id: addressId,
                },
              });
              if (!address) {
                return halt('地址信息异常');
              }
            } else {
              return halt('收货地址必填');
            }
          }

          const count = await tx.userReceiveRights.count({
            where: {
              tokenId: token.id,
            },
          });
          if (Number(count) >= Number(token.asset.rightsLimit)) {
            return halt('超出最大核销限制');
          }

          if (token.asset.rightsType === RightsTypeEnum.NONE) {
          } else if (token.asset.rightsType === RightsTypeEnum.BURN) {
            await this.burn({ id: token.asset?.id, no: token.no });
          } else if (
            token.asset.rightsType === RightsTypeEnum.BURN_AND_REFUND
          ) {
            await this.burn({ id: token.asset?.id, no: token.no });
            const total = Number(token.asset.releasePrice);
            await tx.bill.create({
              data: {
                no: await this.billService.generateOrderNo(),
                total,
                fee: 0,
                type: BillTypeEnum.REFUND,
                status: BillStatusEnum.COMPLETED,
                ownerId: user.id,
                remark: `核销后销毁资产${token.asset.title}#${token.no}，退款${total}元`,
              },
            });
            const owner = await tx.user.findUnique({
              where: {
                id: constants.default.ownerId,
              },
            });
            await tx.user.update({
              where: {
                id: owner.id,
                version: owner.version,
              },
              data: {
                version: {
                  increment: 1,
                },
                balance: {
                  decrement: total,
                },
              },
            });
            await tx.user.update({
              where: {
                id: user.id,
                version: user.version,
              },
              data: {
                version: {
                  increment: 1,
                },
                balance: {
                  increment: total,
                },
              },
            });
          } else {
            return halt('核销类型异常');
          }

          await tx.userReceiveRights.create({
            data: {
              tokenId: token.id,
              userId,
              address,
              status: RightsStatusEnum.PENDING,
            },
          });
        });
      },
    );
  }

  async distribution({ id, params, sort }: AssetDistributionDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    const sql = `
      SELECT #{fields} FROM Token AS t
      LEFT JOIN Asset AS a ON a.id = t.assetId
      LEFT JOIN User AS u ON u.id = t.ownerId
      WHERE t.isDeleted = false
        AND t.assetId = ${id}
        AND t.status != ${TokenStatusEnum.DESTROYED}
        ${!!where.mobile ? `AND u.mobile LIKE '%${where.mobile}%'` : ''}
        ${!!where.realname ? `AND u.realname LIKE '%${where.realname}%'` : ''}
      GROUP BY u.id
      HAVING COUNT(u.id) > 0
    `;

    const res = await this.prisma.$queryRawUnsafe<any>(
      sql.replace('#{fields}', 'COUNT(DISTINCT u.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Asset[]>(
      `${sql.replace(
        '#{fields}',
        `
          u.id, u.realname, u.mobile,
          COUNT(u.id) AS num
        `,
      )}
      ORDER BY num DESC
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = res?.length ?? 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o: any) => ({
        ...o,
        num: Number(o.num),
      })),
    };
  }

  async circulation({ id }: AssetCirculationDto) {
    return await this.prisma.token.count({
      where: {
        assetId: id,
        status: {
          not: TokenStatusEnum.DESTROYED,
        },
        ownerId: { not: constants.default.ownerId },
        isDeleted: false,
      },
    });
  }

  async lock({ targets }: AssetLockDto) {
    return await this.prisma.token.updateMany({
      where: {
        hash: {
          in: targets,
        },
      },
      data: {
        isTradable: false,
      },
    });
  }

  async unLock({ targets }: AssetUnLockDto) {
    return await this.prisma.token.updateMany({
      where: {
        hash: {
          in: targets,
        },
      },
      data: {
        isTradable: true,
      },
    });
  }

  async destroy({ ids }: AssetDestroyDto) {
    return await this.prisma.token.updateMany({
      where: {
        id: {
          in: ids,
        },
      },
      data: {
        isDeleted: true,
        status: TokenStatusEnum.DESTROYED,
      },
    });
  }

  async tokenDestroy(tokenIds, ownerId) {
    return this.prisma.$transaction(async (tx) => {
      const tokens = await tx.token.findMany({
        where: {
          ownerId,
          status: TokenStatusEnum.HOLD,
          isDeleted: false,
          id: {
            in: tokenIds,
          },
        },
      });

      if (tokens.length < 1) {
        return halt('无可销毁资产');
      }

      const validAssets = await tx.asset.findMany({
        where: {
          destroyPoints: {
            gt: 0,
          },
        },
        select: {
          id: true,
          destroyPoints: true,
        },
      });

      if (validAssets.length < 1) {
        return halt('未设置销毁活动');
      }

      const assetIds = [];
      const assetWithPoints = [];
      for (const item of validAssets) {
        assetIds.push(item.id);
        assetWithPoints[item.id] = item.destroyPoints;
      }

      const validTokens = await tx.token.findMany({
        where: {
          ownerId,
          status: TokenStatusEnum.HOLD,
          isDeleted: false,
          id: {
            in: tokenIds,
          },
          assetId: {
            in: assetIds,
          },
        },
      });

      if (validTokens.length < 1) {
        return halt('销毁失败，无有效资产');
      }

      const validTokenIds = [];
      const createDatas = [];
      let validPoints = 0;
      for (const item of validTokens) {
        validTokenIds.push(item.id);
        validPoints += assetWithPoints[item.assetId];

        createDatas.push({
          log: `主动销毁: ${item.no}`,
          type: TokenLogTypeEnum.BURN,
          tokenId: item.id,
          fromId: item.ownerId,
        });
      }

      if (validPoints <= 0) {
        return halt('销毁失败，总积分为0');
      }

      const { count } = await tx.token.updateMany({
        where: {
          ownerId,
          status: TokenStatusEnum.HOLD,
          isDeleted: false,
          id: {
            in: validTokenIds,
          },
        },
        data: {
          status: TokenStatusEnum.DESTROYED,
        },
      });
      // Logger.debug(`共销毁${count}个`);

      if (validTokens.length != count) {
        return halt('销毁失败，请重试');
      }

      const user = await tx.user.findUnique({
        where: {
          id: ownerId,
        },
        select: {
          id: true,
          version: true,
          destroyPoints: true,
        },
      });

      // Logger.debug(`积分：${user.destroyPoints}`);

      try {
        await tx.user.update({
          where: {
            id: ownerId,
            version: user.version,
          },
          data: {
            version: {
              increment: 1,
            },
            destroyPoints: {
              increment: validPoints,
            },
          },
        });
      } catch (e) {
        return halt('资产销毁失败');
      }

      await tx.tokenLog.createMany({
        data: createDatas,
      });
    });
  }

  async pre({ id }: AssetPreDto) {
    const asset = await this.prisma.asset.findUnique({
      where: {
        id,
      },
      select: {
        id: true,
        title: true,
        image: true,
        desc: true,
        detail: true,
        releasePrice: true,
        releaseDate: true,
        limit: true,
        releaseNo: true,
        assetId: true,
        isPreRegistration: true,
        publisher: true,
        creator: true,
      },
    });
    if (!asset) {
      return halt('资产信息异常');
    }
    if (!!asset?.releaseNo) {
      if (!!asset?.isPreRegistration) {
        return halt('请勿重复登记');
      } else {
        const res = await this.caeeService.status([asset.assetId]);
        if (res?.code !== 200) {
          return halt(res?.message);
        }
        const registrationInfo = res?.data[0];
        if (!registrationInfo || registrationInfo?.statusCode === 0) {
          return;
        }
        if (registrationInfo?.statusCode === 40) {
          await this.prisma.asset.update({
            where: {
              id: asset.id,
            },
            data: {
              registrationInfo,
              isPreRegistration: true,
            },
          });
        }
        return registrationInfo;
      }
    }
    const image = await this.caeeService.getImageInfo(asset.image);
    const releaseNo = await this.caeeService.generateSeriesNo();
    const res = await this.caeeService.pre({
      works: {
        platformAppId: this.caeeService.AppId,
        releaseNo,
        itemId: asset.id.toString(),
        itemName: asset.title,
        itemIntro: asset.desc,
        itemDetails: asset.desc,
        itemLabel: '10',
        unitReferPrice: Number(asset.releasePrice) * 100,
        firstReleaseDate: dayjs(asset.releaseDate).format(
          'YYYY-MM-DD HH:mm:ss',
        ),
        planCount: asset.limit,
        mainDesigner: asset.creator.name,
      },
      attachment: {
        releaseImages: [image],
      },
      extension: {},
    });
    if (res?.code !== 200) {
      return halt(res?.message);
    }
    await this.prisma.asset.update({
      where: {
        id: asset.id,
      },
      data: {
        releaseNo,
        seriesNo: res.data?.seriesNo,
        assetId: res.data?.assetId,
        preRegistrationHash: res.data?.dataHash,
      },
    });

    return true;
  }

  async registration({ id }: AssetRegistrationDto) {
    const asset = await this.prisma.asset.findUnique({
      where: { id },
      select: {
        id: true,
        title: true,
        image: true,
        desc: true,
        detail: true,
        releasePrice: true,
        releaseDate: true,
        limit: true,
        releaseNo: true,
        classId: true,
        assetId: true,
        isPreRegistration: true,
        isRegistration: true,
        registrationHash: true,
        publisher: true,
        creator: true,
      },
    });
    if (!asset) {
      return halt('资产信息异常');
    }
    if (!asset?.isPreRegistration) {
      return halt('请先完成登记');
    }
    if (!!asset?.registrationHash || !!asset?.isRegistration) {
      return halt('请勿重复备案');
    }
    const tokenCount = await this.prisma.token.count({
      where: {
        assetId: asset.id,
        txState: ChainTxStateEnum.SUCCESS,
      },
    });
    if (tokenCount < asset.limit) {
      return halt('请先完成铸造和上链');
    }

    const txRes = await this.chain.classesDetail({
      classId: asset.classId,
    });
    if (txRes?.code !== 200) {
      return halt('查询资产合约信息异常');
    }

    const tokens = await this.prisma.token.findMany({
      where: {
        assetId: asset.id,
        txState: ChainTxStateEnum.SUCCESS,
        isRegistration: false,
      },
      select: {
        id: true,
        no: true,
        owner: true,
      },
      take: 1000,
    });

    if (tokens.length <= 0) {
      const res = await this.caeeService.status([asset.assetId]);
      if (res?.code !== 200) {
        return halt(res?.message);
      }
      const registrationInfo = res?.data[0];
      if (!registrationInfo || registrationInfo?.statusCode === 0) {
        return;
      }
      if (registrationInfo?.statusCode === 20) {
        await this.prisma.asset.update({
          where: {
            id: asset.id,
          },
          data: {
            registrationInfo,
            isRegistration: true,
          },
        });
      }
      return true;
    }

    const res = await this.caeeService.registration({
      asset: {
        platformAppId: this.caeeService.AppId,
        assetId: asset.assetId,
        itemId: asset.id.toString(),
        releaseUnitPrice: Number(asset.releasePrice) * 100,
        releaseDate: dayjs(asset.releaseDate).format('YYYY-MM-DD HH:mm:ss'),
        issueCount: asset.limit,
        tokenCount,
        facet: 'NFT',
        tokenList: tokens.map((item) => ({
          tokenId: item.id,
          ownerAddr: item.owner.account,
          sequenceNo: Number(item.no),
        })),
        blockChain: {
          chainId: 'chain-725078401559433216',
          chainType: '数文链',
          consensusAlg: 'BFT',
          encryptionAlg: 'SM2',
        },
        smartContract: {
          scCode: txRes.data.contractAddress,
          scProgramType: 'solidity',
          crossChainProto: 'OTHER',
          scFacet: 'NFT',
        },
      },
      extension: {},
    });

    if (res?.code !== 200) {
      return halt(res?.message);
    }

    await this.prisma.token.updateMany({
      where: {
        id: { in: tokens.map((item) => item.id) },
      },
      data: {
        isRegistration: true,
        registrationHash: res.data.dataHash,
      },
    });

    return true;
  }

  async down({ id }: MarketDownDto) {
    // return this.prisma.token.update({
    //   where: {
    //     status: TokenStatusEnum.ON_SALE,
    //     id: id,
    //   },
    //   data: {
    //     status: TokenStatusEnum.HOLD,
    //   },
    // });
    return await this.prisma.$transaction(async (tx) => {
      await tx.token.update({
        where: {
          id,
          status: TokenStatusEnum.ON_SALE,
        },
        data: {
          status: TokenStatusEnum.HOLD,
        },
      });
      await tx.tokenLog.create({
        data: {
          log: `取消寄售`,
          type: TokenLogTypeEnum.OFF_SALE,
          tokenId: id,
          fromId: 1,
        },
      });
      return true;
    });
  }

  async batchDown({ ids }: MarketBatchDownDto) {
    return this.prisma.token.updateMany({
      where: {
        status: TokenStatusEnum.ON_SALE,
        id: {
          in: ids,
        },
      },
      data: {
        status: TokenStatusEnum.HOLD,
      },
    });
  }

  async batchDown2({ ids }: MarketBatchDownDto) {
    for (const tokenId of ids) {
      try {
        const token = await this.prisma.token.findUnique({
          where: {
            id: Number(tokenId),
            status: TokenStatusEnum.ON_SALE,
          },
        });
        if (token) {
          await this.prisma.token.update({
            where: {
              id: Number(tokenId),
              status: TokenStatusEnum.ON_SALE,
            },
            data: {
              status: TokenStatusEnum.HOLD,
            },
          });

          try {
            await this.prisma.tokenLog.create({
              data: {
                log: `买入${token.price}￥`,
                type: TokenLogTypeEnum.OFF_SALE2,
                tokenId: token.id,
                fromId: token.sellerId,
              },
            });
          } catch (e) {
            console.error(e);
          }
        } else {
          // Logger.debug(`${item.assetId}:BUY:无需更新`);
          // Logger.debug(`${item.assetId}:BUY:结束买入`);
          continue;
        }
      } catch (e) {
        Logger.debug(`[${tokenId}]更新失败`);
        // Logger.debug(`${item.assetId}:BUY:结束买入`);
        continue;
      }
      Logger.debug(`[${tokenId}]更新成功`);
      // Logger.debug(`${item.assetId}:BUY:结束买入`);
    }
  }

  async batchUp({ ids, price }: MarketBatchUpDto) {
    const seller = await this.prisma.token.findFirst({
      where: {
        id: ids[0],
      },
    });
    if (!seller) return halt('卖家信息异常');

    return this.prisma.token.updateMany({
      where: {
        status: TokenStatusEnum.HOLD,
        id: {
          in: ids,
        },
      },
      data: {
        status: TokenStatusEnum.ON_SALE,
        price: price,
        sellAt: new Date(),
        sellerId: seller.ownerId,
      },
    });
  }

  async generateOrderNo() {
    return `${dayjs().format('YYYYMMDDHHmmss')}${await getCode(11)}`;
  }
}
